#include "base_io_port.h"
#include <functional>
#include "stdio.h"
#include <stdlib.h>

#include <sys/file.h>
#include <sys/select.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <string.h>
#include <memory.h>
#include "utils.h"

list<BaseIoPort *> BaseIoPort::mOpenedClientPortList;
mutex BaseIoPort::mOpenedClientPortListMutex;
thread BaseIoPort::mPortProcessThread;
bool BaseIoPort::mStopProcessFlag = false;

event_fd_def BaseIoPort::mEventHandle = eventfd(0, 0);

BaseIoPort::BaseIoPort(int maxTxFifoSize)
{
    mRxCallBack = nullptr;
    mIsOpened = false;
    mName = "";
    mParam = 115200;
    mTxFifo = new FifoBuffer(maxTxFifoSize);
    mHandle = -1;
}

BaseIoPort::~BaseIoPort()
{
    //Close();
    if (mTxFifo)
        delete mTxFifo;
}

bool BaseIoPort::ProcessLoop()
{
    fd_set rfds, wfds;
    int32_t maxfd = 0, fd_cnt = 0;

    struct timeval tv = { 0, 500 * 1000 };

    uint8_t tmp_buf[1024];
    pair<event_fd_def, BaseIoPort *> handle_map[4];
    mOpenedClientPortListMutex.lock();
    int32_t port_cnt = mOpenedClientPortList.size();
    if ((port_cnt <= 0) || (port_cnt >= 4)) {
        mOpenedClientPortListMutex.unlock();
        return false;
    }

    FD_ZERO(&rfds);
    FD_ZERO(&wfds);

    for (BaseIoPort *port : mOpenedClientPortList) {
        if (!port->mIsOpened)
            continue;

        handle_map[fd_cnt].first = port->mHandle;
        handle_map[fd_cnt].second = port;
        fd_cnt++;

        if (maxfd < port->mHandle)
            maxfd = port->mHandle;

        FD_SET(port->mHandle, &rfds);
        if (port->mTxFifo->Empty() == 0)
            FD_SET(port->mHandle, &wfds);
    }

    // no fd to select
    if (fd_cnt == 0) {
        mOpenedClientPortListMutex.unlock();
        return false;
    }

    // add tx event signal event
    FD_SET(mEventHandle, &rfds);
    if (maxfd < mEventHandle)
        maxfd = mEventHandle;


    int ret = select((int32_t)maxfd + 1, &rfds, &wfds, NULL, &tv);
    if (ret > 0) {
        for (int32_t i = 0; i < fd_cnt; i++) {
            event_fd_def fd = handle_map[i].first;
            BaseIoPort *port = handle_map[i].second;
            if (FD_ISSET(fd, &rfds)) {

                int rx_n = read(fd, (char*)tmp_buf, sizeof(tmp_buf));
    
                if ((rx_n > 0) && port->mRxCallBack) {
                    port->mRxCallBack(port->mRxCallBackParam, tmp_buf, rx_n);
                } else {
                    mOpenedClientPortListMutex.unlock();
                    FLOGD("%s fd(%d) error, close !!",port->mName.c_str(),port->mHandle);
                    port->Close();
					FD_CLR(fd, &rfds);
                    ret = -1;
                    return false;
                }
            }

            if (FD_ISSET(fd, &wfds)) {
                int tx_n = port->mTxFifo->Pop(tmp_buf, sizeof(tmp_buf));
                if (tx_n > 0) {
                    int v = write(fd, (char*)tmp_buf, (size_t)tx_n);
                    if (v < 0) ret = -1;
                }
            }
        }

        if (FD_ISSET(mEventHandle, &rfds)) {
            ssize_t v = read(mEventHandle, tmp_buf, sizeof(uint64_t));
            if (v < 0) ret = -1;
        }
    }
    mOpenedClientPortListMutex.unlock();
    return ret >= 0;
}

int32_t BaseIoPort::Send(const uint8_t *data, int32_t len)
{
    if (!mIsOpened)
        return -1;

    int32_t size = mTxFifo->Size();
    int32_t ret = mTxFifo->Push(data, len, 0);

    // fifo is empty before push, notify wait function
    if (size == 0) {
        uint64_t u = 1;
        ssize_t v = write(mEventHandle, &u, sizeof(uint64_t));
        if (v < 0) ret = -1;
    }
    return ret;
}

void BaseIoPort::Start()
{
    if(!mIsThreadHaveStart){
        mStopProcessFlag = false;
        mPortProcessThread = thread([]() {
            SetThreadName("baseio_port");
            while (!mStopProcessFlag) {
                if (!BaseIoPort::ProcessLoop())
                    this_thread::sleep_for(chrono::milliseconds(100));
            }
        });
        mIsThreadHaveStart = true;
    }else{
        FLOGD("base io port only need to start once, fd size %d", mOpenedClientPortList.size());
    }

}

void BaseIoPort::Stop()
{
    mStopProcessFlag = true;
    if (mPortProcessThread.joinable())
        mPortProcessThread.join();
    mIsThreadHaveStart = false;
}

int32_t BaseIoPort::ReOpen()
{
    Close();
    return Open(mName, mParam);
}



